﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using eDonkeyAPI.Models;

namespace eDonkeyAPI.Controllers
{
    public class SchedulerController : ApiController
    {

        private mydbEntities db = new mydbEntities();

        /*
         * Important Notes:
         * - We don't want to spend time on checking if courses list does not contain duplications.
         *   So please, we suppose that you will not send courses list like that.
        */
        [HttpGet]
        public List<Group> GetSchedule([FromUri]List<string> courses, [FromUri]int rank = 1,
                                       [FromUri]List<string> freeDays = null, [FromUri]int numberOfFreeDays = 0, 
                                       [FromUri]List<string> choosenGroups = null, [FromUri]int dontShowOverlappedSchedules = 0, 
                                       [FromUri]int goPrev = 0, [FromUri]int noRunning = 0)
        {            
            List<Group> groups = new List<Group>();
            int i, maxNumOfRanks = 1, currentRank = rank, isGroupHasStartTime = 0;
            foreach (string course in courses)
            {
                ICollection<Group> igroups = db.Courses.Include(c => c.Groups).FirstOrDefault(c => c.Number == course).Groups;
                if (igroups == null || igroups.Count == 0)
                {
                    continue;
                }
                foreach (Group group in igroups)
                {
                    group.PlaceTimes = db.PlaceTimes.Where(pt => pt.GroupId == group.Id).ToList();
                    foreach (PlaceTime placeTime in group.PlaceTimes)
                    {
                        placeTime.Group = null;
                    }
                    group.Course.Groups = null;
                    group.Course.Semester = null;
                    group.Course.Teacher = null;
                    groups.Add(group);
                }
            }

            Dictionary<string, List<Group>> lessonsList =
                                 new Dictionary<string, List<Group>>();
            List<int> indexesList = new List<int>();  // for the indexes in getCompinationsByRank.
            i = 0;
            if (groups == null)
            {
                return null;
            }
            KeyValuePair<int, int>[] hourConstrainsArray = new KeyValuePair<int, int>[6];
            if (freeDays.Count != 12)
            {
                hourConstrainsArray = null;
            }
            else
            {
                for (int j = 0; j < 6; j++)
                {
                    int fromHour, toHour;
                    if (freeDays[2 * j].Equals("None") == true || freeDays[2 * j + 1].Equals("None") == true)
                    {
                        hourConstrainsArray[j] = new KeyValuePair<int, int>(0, 0);
                        continue;
                    }
                    if (freeDays[2 * j].Equals("Any") == true)
                    {
                        fromHour = -1;
                    }
                    else
                    {
                        fromHour = convertHourToIndex(freeDays[2 * j]);
                    }
                    if (freeDays[2 * j + 1].Equals("Any") == true)
                    {
                        // 100 is bigger than all the hour indexes.
                        toHour = 100;
                    }
                    else
                    {
                        toHour = convertHourToIndex(freeDays[2 * j + 1]);
                    }
                    hourConstrainsArray[j] = new KeyValuePair<int, int>(fromHour, toHour);
                }
            }

            //calculating number of free days which exist in hourConstrainsArray, in order to know the number
            // of remained free days which must be in the schedule.
            int existsFreeDaysNumber = 0;
            if (hourConstrainsArray != null)
            {
                for (int j = 0; j < 6; j++)
                {
                    if (hourConstrainsArray[j].Key == -1 && hourConstrainsArray[j].Value == -1)
                    {
                        existsFreeDaysNumber++;
                    }
                }
            }

            //ADDED: added those two for's to check if a group has no times like project or ma3badah.
            foreach (Group group in groups)
            {
                isGroupHasStartTime = 0;
                if (group.PlaceTimes == null)
                {
                    continue;
                }

                foreach (PlaceTime time in group.PlaceTimes)
                {
                    if (string.IsNullOrWhiteSpace(time.StartTime))
                    {
                        isGroupHasStartTime = 1;
                    }
                }
                if (isGroupHasStartTime == 1 || isGroupMaintainHourConstrains(group,  hourConstrainsArray) == false
                    || (groupIsNotChoosed(group, choosenGroups)))
                {
                    continue;
                }
                
                string courseId = Convert.ToString(group.CourseId);

                if (lessonsList.ContainsKey(courseId + group.Role) == false)
                {
                    lessonsList.Add(courseId + group.Role, new List<Group>());
                }
                lessonsList[courseId + group.Role].Add(group);
            }

            foreach (KeyValuePair<string, List<Group>> listOfGroups in lessonsList)
            {
                if (listOfGroups.Value.Count > 0)
                {
                    indexesList.Add(listOfGroups.Value.Count);
                    maxNumOfRanks *= listOfGroups.Value.Count;
                }
            }

            List<Group> scheduledGroups = new List<Group>();
            Group indicatingCurrentRankGroup = new Group();

            List<int> compIndexes = new List<int>();
            if (rank > maxNumOfRanks)
            {
                indicatingCurrentRankGroup.Id = -1;
                indicatingCurrentRankGroup.Number = currentRank;
                indicatingCurrentRankGroup.Id = maxNumOfRanks;
                scheduledGroups.Add(indicatingCurrentRankGroup);
                return scheduledGroups;
            }
            compIndexes = getCombinationByRank(indexesList, rank);

           while (true)
            {
                if (currentRank > maxNumOfRanks || currentRank < 1)
                {
                    if (currentRank < 1)
                    {
                        currentRank = maxNumOfRanks + 2;
                    }
                    indicatingCurrentRankGroup.Id = -1;
                    indicatingCurrentRankGroup.Number = currentRank;
                    indicatingCurrentRankGroup.Id = maxNumOfRanks;
                    scheduledGroups.Add(indicatingCurrentRankGroup);
                    return scheduledGroups;
                }
                compIndexes = getCombinationByRank(indexesList, currentRank);

                if (compIndexes.Count == 0)
                {
                    return null;
                }

                i = 0;
                foreach (KeyValuePair<string, List<Group>> listOfGroups in lessonsList)
                {
                    if (listOfGroups.Value.Count > 0)
                    {
                        scheduledGroups.Add(listOfGroups.Value[compIndexes[i]]);
                        ++i;
                    }
                }
                if ( dontShowOverlappedSchedules == 0 || isOverlapping(scheduledGroups) == false)
                {
                    if (noRunning == 0 || isScheduleWithRunning(scheduledGroups) == false)
                    {
                        if (numberOfFreeDays <= existsFreeDaysNumber ||
                            isHavingNeededFreeDays(scheduledGroups, numberOfFreeDays - existsFreeDaysNumber))
                        {
                            break;
                        }
                    }
                }
                if (goPrev == 1)
                {
                    currentRank--;
                }
                else
                {
                    currentRank++;
                }
                scheduledGroups = new List<Group>();
            }
            indicatingCurrentRankGroup.Id = maxNumOfRanks;
            indicatingCurrentRankGroup.Number = currentRank;
            scheduledGroups.Add(indicatingCurrentRankGroup);
            return scheduledGroups;
        }

        private List<int> getCombinationByRank(List<int> listsLengths, int rank)
        {
            int currentIndex = 0;
            int currentRank = rank;
            int mulLengths = 1;
            int compLength = listsLengths.Count;
            List<int> comp = new List<int>();
            foreach (int current in listsLengths)
            {
                if (current != 0)
                {
                    mulLengths *= current;
                }
            }
            for (int i = 0; i < compLength; i++)
            {
                if (listsLengths[i] != 0)
                {
                    mulLengths = mulLengths / listsLengths[i];
                    currentIndex = (currentRank - 1) / mulLengths;
                    comp.Add(currentIndex);
                    currentRank -= currentIndex * mulLengths;
                }
                else
                {
                    // this group is empty.
                    comp.Add(-1);
                }
            }
            return comp;
        }

        public bool isOverlapping(List<Group> groups)
        {           if(groups == null || groups.Count == 0)
            {
                return false;
            }
            string[] days = { "א", "ב", "ג", "ד", "ה", "ו", "ז" };
            int[,] overlappingMatrix = new int[28, 6];
            for(int i = 0; i < 28; i++)
            {
                for(int j = 0; j < 6; j++)
                {
                    overlappingMatrix[i,j] = 0;
                }
            }

            foreach (Group group in groups)
            {
                if (group.PlaceTimes == null || group.PlaceTimes.Count == 0)
                {
                    continue;
                }
                foreach(PlaceTime placeTime in group.PlaceTimes)
                {
                    int startRow = convertHourToIndex(placeTime.StartTime);
                    int endRow = startRow + Convert.ToInt32(placeTime.Duration) * 2;
                    int col = convertDayToInt(days, placeTime.Day);
                    for (int k = startRow; k < endRow; k++)
                    {
                        if(overlappingMatrix[k,col] != 0)
                        {
                            return true;
                        }
                        overlappingMatrix[k, col] = 1;
                    }
                }
            }

            return false;
        }


        public bool isBuildingsTheSame(string building1, string building2)
        {
            if (building1.Length == null || building2 == null || building1.Length == 0 || building2.Length == 0)
            {
                return true;
            }
            List<double> cor1 = getBuildingCordinates(building1);
            List<double> cor2 = getBuildingCordinates(building2);
            for (int i = 0; i < 2; i++)
            {
                if (cor1[i] != cor2[i])
                {
                    return false;
                }
            }

                return true;
        }

        public bool isScheduleWithRunning(List<Group> groups)
        {
            if (groups == null || groups.Count == 0)
            {
                return false;
            }
            string[] days = { "א", "ב", "ג", "ד", "ה", "ו", "ז" };
            List<string>[,] scheduleMatrix = new List<string>[28, 6];
            for (int i = 0; i < 28; i++)
            {
                for (int j = 0; j < 6; j++)
                {
                    scheduleMatrix[i, j] = new List<string>();
                }
            }

            foreach (Group group in groups)
            {
                if (group.PlaceTimes == null || group.PlaceTimes.Count == 0)
                {
                    continue;
                }
                foreach (PlaceTime placeTime in group.PlaceTimes)
                {
                    int startRow = convertHourToIndex(placeTime.StartTime);
                    int endRow = startRow + Convert.ToInt32(placeTime.Duration) * 2;
                    int col = convertDayToInt(days, placeTime.Day);
                    if (scheduleMatrix[startRow, col].Count > 0)
                    {
                        foreach (string building in scheduleMatrix[startRow, col])
                        {
                            if (isBuildingsTheSame(building, placeTime.Place) == false)
                            {
                                return true;
                            }
                        }
                    }
                    scheduleMatrix[endRow, col].Add(placeTime.Place);
                }
            }
            return false;
        }

        bool isHavingNeededFreeDays(List<Group> groups, int neededFreeDays)
        {
            int[] existingDays = { 0, 0, 0, 0, 0, 0, 0};
            string[] days = { "א", "ב", "ג", "ד", "ה", "ו", "ז" };
            int counter = 0;
            foreach (Group group in groups)
            {
                foreach (PlaceTime placeTime in group.PlaceTimes)
                {
                    if (placeTime.Day.Length != 0)
                    {
                        existingDays[convertDayToInt(days, placeTime.Day)] = 1;
                    }

                }
            }
            for (int i = 0; i < 6; i++)
            {
                if (existingDays[i] == 0)
                {
                    counter++;
                }
            }
            if (counter >= neededFreeDays)
            {
                return true;
            }
            return false;
        }

        public int convertDayToInt(string[] days, string day)
        {
            for (int i = 0; i < 7; i++)
            {
                if (days[i][0] == day[0])
                {
                    return i;
                }
            }
            return -1;
        }


        public static int convertHourToIndex(string hour)
        {
            double index;
            if (hour.Length < 2)
            {
                return -1;
            }

            if (hour[1] == '.')
            {
                index = hour[0] - '0';
                if (hour[2] == '3')
                {
                    index += 0.5;
                }
            }
            else
            {
                index = (hour[0] - '0') * 10 + (hour[1] - '0');
                if (hour[3] == '3')
                {
                    index += 0.5;
                }
            }
            return (int)((index - 7.0) * 2.0);
        }


        public bool isGroupMaintainHourConstrains(Group group = null, KeyValuePair<int, int>[] hourConstrainsArray = null)
        {
            if (hourConstrainsArray == null || group == null)
            {
                return true;
            }
            string[] days = { "א", "ב", "ג", "ד", "ה", "ו", "ז" };
            for (int i = 0; i < 6; i++)
            {
                foreach (PlaceTime placeTime in group.PlaceTimes)
                {
                    int constraintStart = hourConstrainsArray[i].Key;
                    int constraintEnd = hourConstrainsArray[i].Value;
                    int placeTimeStart = convertHourToIndex(placeTime.StartTime);
                    int placeTimeEnd = convertHourToIndex(placeTime.StartTime) + 2 * Convert.ToInt32(placeTime.Duration);
                    if (constraintStart == placeTimeStart && constraintEnd == placeTimeEnd)
                    {
                        return false;
                    }
                    if (convertDayToInt(days, placeTime.Day) == i &&
                        !((constraintStart <= placeTimeStart && constraintEnd <= placeTimeStart) ||
                        (constraintStart >= placeTimeEnd && constraintEnd >= placeTimeEnd)))
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        public bool groupIsNotChoosed(Group group, List<string> choosenGroups = null) 
        {
            if(choosenGroups == null || choosenGroups.Count == 0)
            {
                return false;
            }
            if(choosenGroups.Contains(group.Id.ToString()))
            {
                return true;
            }
            return false;
        }

        public bool englishDayAndHebrewDayAreWEqual(string engDay, string hebDay)
        {
            if(hebDay.Length == 0)
            {
                return false;
            }
            if (engDay == "Sunday" && hebDay[0] == 'א')
            {
                return true;
            }
            if (engDay == "Monday" && hebDay[0] == 'ב')
            {
                return true;
            }
            if (engDay == "Tuesday" && hebDay[0] == 'ג')
            {
                return true;
            }
            if (engDay == "Wednesday" && hebDay[0] == 'ד')
            {
                return true;
            }
            if (engDay == "Thursday" && hebDay[0] == 'ה')
            {
                return true;
            }
            if (engDay == "Friday" && hebDay[0] == 'ו')
            {
                return true;
            }
            if (engDay == "Saturday" && hebDay[0] == 'ז')
            {
                return true;
            }
            return false;
        }

        public List<double> getBuildingCordinates([FromUri]string name)
        {
            List<double> cor = new List<double>();
            if (name == null || name == "")
            {
                return cor;
            }
            if (name.IndexOf("טאוב") != -1)
            {
                cor.Add(32.777758);
                cor.Add(35.021689);
            }
            if (name.IndexOf("רבין") != -1)
            {
                cor.Add(32.779116);
                cor.Add(35.022199);
            }
            if (name.IndexOf("בורוביץ") != -1)
            {
                cor.Add(32.779513);
                cor.Add(35.022832);
            }
            if (name.IndexOf("ח.בניה") != -1)
            {
                cor.Add(32.779247);
                cor.Add(35.021415);
            }
            if (name.IndexOf("דייויס") != -1)
            {
                cor.Add(32.774128);
                cor.Add(35.022574);
            }
            if (name.IndexOf("ה.כימית") != -1)
            {
                cor.Add(32.778128);
                cor.Add(35.027289);
            }
            if (name.IndexOf("הנ.מזון") != -1)
            {
                cor.Add(32.778164);
                cor.Add(35.026613);
            }
            if (name.IndexOf("ויזין") != -1)
            {
                cor.Add(32.778164);
                cor.Add(35.026613);
            }
            if (name.IndexOf("נהול") != -1)
            {
                cor.Add(32.774222);
                cor.Add(35.023711);
            }
            if (name.IndexOf("בלומפילד") != -1)
            {
                cor.Add(32.774069);
                cor.Add(35.024237);
            }
            if (name.IndexOf("אולמן") != -1)
            {
                cor.Add(32.777181);
                cor.Add(35.023797);
            }
            if (name.IndexOf("פיסיקה") != -1)
            {
                cor.Add(32.776559);
                cor.Add( 35.025385);
            }
            if (name.IndexOf("סמינר' ב") != -1)
            {
                cor.Add(32.776559);
                cor.Add(35.025385);
            }
            if (name.IndexOf("כימיה") != -1)
            {
                cor.Add(32.777682);
                cor.Add(35.026109);
            }
            if (name.IndexOf("אמדו") != -1)
            {
                cor.Add(32.778024);
                cor.Add(35.023288);
            }
            if (name.IndexOf("סגו") != -1)
            {
                cor.Add(32.778173);
                cor.Add(35.022815);
            }
            if (name.IndexOf("ח.סטודיו") != -1)
            {
                cor.Add(32.778173);
                cor.Add(35.022815);
            }
            if (name.IndexOf("מעב.מחשב") != -1)
            {
                cor.Add(32.778173);
                cor.Add(35.022815);
            }
            if (name.IndexOf("שרמן הור") != -1)
            {
                cor.Add(32.77659);
                cor.Add(35.027139);
            }
            if (name.IndexOf("ביו-רפוא") != -1)
            {
                cor.Add(32.775282);
                cor.Add(35.026436);
            }
            if (name.IndexOf("ב.רפפורט") != -1)
            {
                cor.Add(32.834191);
                cor.Add(34.983838);
            }
            if (name.IndexOf("חומרים") != -1)
            {
                cor.Add(32.775539);
                cor.Add(35.025423);
            }
            if (name.IndexOf("הומניסטי") != -1)
            {
                cor.Add(32.777605);
                cor.Add(35.025015);
            }
            if (name.IndexOf("חדר כושר") != -1)
            {
                cor.Add(32.779152);
                cor.Add(35.019082);
            }
            if (name.IndexOf("מעונ'קסל") != -1) //?????
            {
                cor.Add(32.819036);
                cor.Add(34.986176);
            }
            if (name.IndexOf("אולם ב") != -1) //
            {
                cor.Add(32.776252);
                cor.Add(35.022794);
            }
            if (name.IndexOf("ב'ס רעות") != -1)//?????
            {
                cor.Add(32.826488);
                cor.Add(34.976319);
            }
            if (name.IndexOf("בריכה") != -1)
            {
                cor.Add(32.779774);
                cor.Add(35.019329);
            }
            if (name.IndexOf("מרכז ימי") != -1) //?????? not assigned.
            {
                cor.Add(32.779116);
                cor.Add(35.022199);
            }
            if (name.IndexOf("נשר") != -1)
            {
                cor.Add(32.77203);
                cor.Add(35.048425);
            }
            if (name.IndexOf("צ'ק פוסט") != -1)
            {
                cor.Add(32.790954);
                cor.Add(35.033491);
            }
            if (name.IndexOf("מג.חיצונ") != -1) // ?????
            {
                cor.Add(32.782386);
                cor.Add(35.021641);
            }
            if (name.IndexOf("מג.טניס") != -1)
            {
                cor.Add(32.77945);
                cor.Add(35.018272);
            }
            if (name.IndexOf("מר.סקווש") != -1) //?????
            {
                cor.Add(32.779152);
                cor.Add(35.019135);
            }
            if (name.IndexOf("אצ.אתלטי") != -1)
            {
                cor.Add(32.782499);
                cor.Add(35.021474);
            }
            if (name.IndexOf("לובי-רפו") != -1) // ????
            {
                cor.Add(32.834191);
                cor.Add(34.983838);
            }
            if (name.IndexOf("מ.בולדר") != -1)
            {
                cor.Add(32.78962);
                cor.Add(35.040443);
            }
            if (name.IndexOf("ב.סטודנט") != -1)
            {
                cor.Add(32.776523);
                cor.Add(35.022794);
            }
            if (name.IndexOf("מקלט אול") != -1)
            {
                cor.Add(32.777181);
                cor.Add(35.023797);
            }
            if (name.IndexOf("ק.טבעון") != -1)// ????
            {
                cor.Add(32.779116);
                cor.Add(35.022199);
            }
            return cor;
        }
    }
}
